#include <sys/socket.h>
#include <netinet/in.h>

#include <stdio.h>
#include <getopt.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <libubox/ustream.h>
#include <libubox/uloop.h>
#include <libubox/usock.h>

#include <openssl/rsa.h>
#include <openssl/err.h>
#include <openssl/pem.h>
#include <openssl/md5.h>

#include "cmd.h"
#include "utils.h"

#define MAX_LEN
#define TOKEN_FILE "token.dat"
//#define KUSER_PRIVATE_KEY "kusrpriv_pkcs8.key"
//#define KUSER_PRIVATE_KEY "clipri.key"
#define KUSER_PRIVATE_KEY "kusrpriv.key"

#define DIGTOKEN_TYPE_ENCRYPT	0xD0
#define DIGTOKEN_TYPE_HASH		0xD1

static const char *port = "10000";
static const char *host = "127.0.0.1";
static struct ustream_fd sfd;
static uint8_t req_buf[1024];
static RSA *prikey;

static int request_digkey_token(struct ustream *s)
{
	int len;
	struct cmd_hdr *hdr = (struct cmd_hdr *)req_buf;
	uint8_t *token = hdr->data;
	uint8_t *p = token;
	FILE *fp;

	hdr->bType = CMD_REQ_DIGKEY_TOKEN;
	
	fp = fopen(TOKEN_FILE, "rb");
	if (!fp) {
		err("open token.dat failed\n");
		return 1;
	}
	while (!feof(fp)) {
		if (!(len = fread(p, sizeof(uint8_t), 512, fp)))
			break;
		p += len;
	}

	len = p - token;
	hdr->wLength = len;

	dump("token", token, len);

	ustream_write(s, (char *)hdr, len + CMD_HDR_LEN, false);
	fclose(fp);

	return 0;
}

static int request_phone_info(struct ustream *s)
{
	struct cmd_hdr *hdr = (struct cmd_hdr *)req_buf;
	struct req_phone_info *req = (struct req_phone_info *)hdr->data;

	memset(req, 0, sizeof(*req));

	hdr->bType = CMD_REQ_PHONE_INFO;
	hdr->wLength = sizeof(*req);
	req->dwUserID = 0x11223344;
	memset(req->strBTAddress, 0x55, 6);
	memset(req->strMACAddress, 0x55, 6);

	ustream_write(s, (char *)hdr, sizeof(*req) + CMD_HDR_LEN, false);

	return 0;
}

static int request_random_number(struct ustream *s)
{
	struct cmd_hdr *hdr = (struct cmd_hdr *)req_buf;
	hdr->bType = CMD_REQ_RANDOM_NUM;
	hdr->wLength = 0;

	ustream_write(s, (char *)hdr, CMD_HDR_LEN, false);
	return 0;
}

static int request_random_encrypt(struct ustream *s, uint8_t *random, int len)
{
	//int flen;
	//uint8_t *buf;
	int ret;
	struct cmd_hdr *hdr = (struct cmd_hdr *)req_buf;
	hdr->bType = CMD_REQ_ENCRYPT_RANDOM_NUM;

	dump("andom number", random, len);

	//flen = RSA_size(prikey);
	ret = RSA_private_encrypt(len, random, hdr->data, prikey, RSA_PKCS1_PADDING);
	if (ret <= 0) {
		err("encrypt failed");
		return 1;
	}
	hdr->wLength = ret;

	dump("encrypt random number", hdr->data, ret);

	ustream_write(s, (char *)hdr, ret + CMD_HDR_LEN, false);
	
	return 0;
}

static int request_opendoor(struct ustream *s)
{
	struct cmd_hdr *hdr = (struct cmd_hdr *)req_buf;
	struct req_open_door *req = (struct req_open_door *)hdr->data;
	hdr->bType = CMD_REQ_OPENDOOR;
	hdr->wLength = sizeof(struct req_open_door);

	req->bSubDoorID = 1;
	req->wRSSI = 249;

	ustream_write(s, (char *)hdr, sizeof(struct req_open_door) + CMD_HDR_LEN, false);
	
	return 0;
}
static void handle_response(struct ustream *s, void *data, int len)
{
	int length;
	int type;
	uint8_t *val;

	struct cmd_hdr *hdr = data;
	type = hdr->bType;
	length = hdr->wLength;
	val = hdr->data;

	dbg("%s %d: type %d, length %d, len(exclue hdr) %d", 
		__func__, __LINE__, type, length, len);
	switch (type) {
	case CMD_RSP_DOORID:
	{
		struct resp_door_id *resp = (struct resp_door_id *)val;
		err("commid: %x, doorid: %x, subdoornum %d", 
				resp->dwCommID, resp->dwDoorID, resp->bSubDoorNum);
		request_digkey_token(s);	// comment to emnulate timeout
		break;
	}
	case CMD_RSP_DIGKEY_TOKEN:
	{	err("digkey resp\n");
		request_phone_info(s);
		break;
	}

	case CMD_RSP_PHONE_INFO:
	{
		request_random_number(s);
		break;
	}
	case CMD_RSP_RANDOM_NUM:
		err("resp random num\n");
	{	request_random_encrypt(s, val, length);
		break;
	}
	case CMD_RSP_ENCRYPT_RANDOM_NUM:
	{
		request_opendoor(s);
	}
		break;

	case CMD_RSP_OPENDOOR:
		err("opendoor successfully");
		uloop_end();
		break;
	}

}

static void client_read_cb(struct ustream *s, int bytes)
{
	uint8_t *data;
	int len;

	do {
		data = (uint8_t *)ustream_get_read_buf(s, &len);
		if (!data)
			break;

		handle_response(s, data, len);

		ustream_consume(s, len);
	} while(1);

	if (s->w.data_bytes > 256 && !ustream_read_blocked(s)) {
		err("Block read, bytes: %d\n", s->w.data_bytes);
		ustream_set_read_blocked(s, true);
	}
}

static void client_close()
{
	err("Connection closed\n");
	ustream_free(&sfd.stream);
	uloop_end();
}

static void client_notify_write(struct ustream *s, int bytes)
{
	err("Wrote %d bytes, pending: %d\n", bytes, s->w.data_bytes);

	if (s->w.data_bytes < 128 && ustream_read_blocked(s)) {
		err("Unblock read\n");
		ustream_set_read_blocked(s, false);
	}
}

static void client_notify_state(struct ustream *s)
{
	if (!s->eof)
		return;

	err("eof!, pending: %d\n", s->w.data_bytes);
	if (!s->w.data_bytes)
		return client_close();

}

static void init_request()
{
	struct cmd_hdr *hdr = (struct cmd_hdr *)req_buf;

	hdr->bType = CMD_REQ_DOORID;
	hdr->wLength = 0;

	ustream_write(&sfd.stream, (char *)hdr, CMD_HDR_LEN, false);
}

static int run_client(void)
{
	int fd;
	FILE *private_file;

	uloop_init();

	err("connecting to %s:%s", host, port);
	fd =  usock(USOCK_TCP | USOCK_IPV4ONLY | USOCK_NUMERIC | USOCK_NONBLOCK, host, port);
	if (fd < 0)
		return -1;

	sfd.stream.notify_read = client_read_cb;
	sfd.stream.notify_state = client_notify_state;
	sfd.stream.notify_write = client_notify_write;

	CRYPTO_malloc_init(); 
	OpenSSL_add_all_algorithms(); 

	if (!(private_file = fopen(KUSER_PRIVATE_KEY, "rb"))) {
		err("cannot load public key file\n");
		return 1;
	}

	//prikey = PEM_read_RSAPrivateKey(private_file, NULL, NULL, NULL);
	err("opening %s", KUSER_PRIVATE_KEY);
	prikey = PEM_read_RSAPrivateKey(private_file, NULL, NULL, NULL);
	if (!prikey) {
		err("cannot load private key\n");
		return 1;
	}

	ustream_fd_init(&sfd, fd);	

	init_request();

	uloop_run();
	uloop_done();

	return 0;
}

static int usage(const char *name)
{
	err("Usage: %s [-H <ip>] [-p <port>]\n", name);

	return 0;
}

int main(int argc, char **argv)
{
	int ch;

	while ((ch = getopt(argc, argv, "p:H:h")) != -1) {
		switch(ch) {
		case 'p':
			port = optarg;
			break;
		case 'H':
			host = optarg;
			break;
		case 'h':
			/* fall through */
		default:
			return usage(argv[0]);
		}
	}

	return run_client();
}
